from apps.logger_config import logger
import re

import numpy as np
import pandas as pd
import requests
import json

from pandas import DataFrame

from apps.nacos import get_service_instance
from apps.utils import code_handle


# TODO 衍生变量处理
# var: 需要衍生的变量名
# data_df: 待写入的DataFrame
# dataset_df_dic: 所有原始数据集的DataFrame字典
# all_variable_df: 所有变量设置信息DataFrame
# sheet_name: 当前数据集对应的sheet名
# derived_type: 衍生变量类型  1: 常量   2: 变量  3: 自定义   4: 按选项生成
# sheet_to_domain_dict: sheet名与domain的映射字典， 合并表格后需要传入该参数， 合并表格时传入的是空
# sheet_to_domain_dict用处：当一个变量的衍生方法为变量时， 比如衍生为：原始/生命体征/WEIGHT_VSORRES， 此时应该取的是合并后的数据集的VS表的WEIGHT_VSORRES字段
def derived_variable_handling(var: str, data_df: DataFrame, dataset_df_dic: dict[str, DataFrame],
                              all_variable_df: DataFrame, sheet_name: str, derived_type: int, sheet_to_domain_dict: dict):
    # 1. 按选项生成
    if derived_type == 4:  # 按选项生成
        data_df = generate_by_option_handling(var, data_df, all_variable_df)
        return data_df

    # 2. 获取衍生方法
    _code_df = all_variable_df.loc[all_variable_df["variable"] == var]["derived_method"]
    code = str(np.array(_code_df)[0])
    logger.info(var + "的衍生方法================>\n" + code)

    # 3. 处理常量
    # if derived_type == 1:  # 常量
    #     data_df[var] = code
    #     return data_df

    # 4. 处理变量
    if derived_type == 2:  # 变量
        code_split = re.split(r'[/,，]', code)
        source = code_split[0]  # 源变量的来源， ’原始‘或者’project‘
        domain = code_split[1]

        # 衍生为：原始/生命体征/WEIGHT_VSORRES， 此时应该取的是合并后的数据集的VS表的WEIGHT_VSORRES字段
        if source == "原始" and sheet_to_domain_dict:
            domain = sheet_to_domain_dict[domain]

        source_variable = code_split[2]
        data_df[var] = dataset_df_dic[domain][source_variable]
        return data_df

    # 5. 处理自定义衍生方法，解析衍生方法，得到变量和表达式
    # variable_lst: ["DM.SITEID", "DM.SUBJID"]
    # _params: ["X1","X2"]
    # expression: "if (X1 == 1) X1+X2"
    variable_lst, _params, expression = code_handle(code)

    logger.info("返回参数:" + str(variable_lst))
    # 3. 如果handle函数不需要参数, 返回一个常量
    if variable_lst is None:
        return
    if len(variable_lst) == 0:
        if isinstance(expression, str):
            data_df[var] = expression
        return

    # 4. 解析出每个属性(域名_变量名)对应的series, 并替换
    for i in range(len(variable_lst)):
        variable = variable_lst[i].split("/")
        source = variable[0]  # 源变量的来源， ’原始‘或者’project‘
        domain = variable[1]
        field = variable[2]
        # 合并表格的时候调用此函数衍生关键变量， 关键变量的来源应该是来自当前表格
        dataset_df: DataFrame = dataset_df_dic[sheet_name]

        # 如果是合并表格后获取衍生变量， 且source='原始', 变量的来源应该是合并后的数据集
        if source == "原始" and sheet_to_domain_dict:
            domain = sheet_to_domain_dict[domain]
            dataset_df = dataset_df_dic[domain]

        variable_lst[i] = list(dataset_df[field])

    # 5. 将参数列表传入,获取返回值
    param = {}
    for j in range(len(variable_lst)):
        param[_params[j]] = variable_lst[j]
    try:
        # 此时传入的参数类似{x1:[1,2,3], x2:[4,5,6]}
        # 一次性传入全部数据进行衍生，避免多次调用远程接口
        res = derived_variable_generate(expression, param)
        data_df[var] = res
    except Exception as e:
        logger.error("========衍生变量处理出错===========" + var)
        logger.error(e)


# 处理NaN
def handle_nan(value):
    if isinstance(value, float) and value != value:  # 利用NaN不等于自身的特性进行检查
        return None
    return value


# 调用aviator-service接口进行衍生变量处理
def derived_variable_generate(exp, env: dict):
    service_name = 'aviator-service'
    service_instance = get_service_instance(service_name)  # 获取服务实例
    if service_instance:
        logger.info("服务地址: {} 服务端口: {}".format(service_instance['ip'], service_instance['port']))
    else:
        logger.error("aviator-service服务未注册")

    # 调用服务示例
    endpoint = 'aviator-service/aviator/derived'  # 服务端点
    ip, port = service_instance['ip'], service_instance['port']
    url = f"http://{ip}:{port}/{endpoint}"
    # print("-------------------url----------------")
    # print(url)

    # 将NaN替换为None，以便正确转换为JSON中的null
    env_clean = {k: [handle_nan(v) for v in values] for k, values in env.items()}

    data = {
        "exp": exp,
        "env": env_clean
    }
    json_data = json.dumps(data)
    response = requests.post(url, data=json_data, headers={'Content-Type': 'application/json'})

    # 解析JSON响应
    response_json = response.json()
    logger.info('----衍生接口响应结果-----：')
    logger.info(response_json)
    # 返回响应中的data属性
    return response_json.get('data')  # 使用 .get() 避免KeyError异常


# 按选项生成
# var: 需要衍生的变量名
# data_df: 待写入的DataFrame
# all_variable_df: 所有变量设置信息DataFrame
def generate_by_option_handling(var, data_df, all_variable_df):
    # 1. 获取该变量对应的受控术语（选项）， 如果是--TESTCD变量， 还需要获取--TEST变量的选项
    code_dict = {}
    if var.endswith('TESTCD'):
        # code-应用名称-数值含义-展示顺序-匹配数据-递交值
        # C20197@#$男性@#$2@#$2@#$2@#$M |-| C16576@#$女性@#$1@#$1@#$1@#$F
        testcd_code = np.array(all_variable_df.loc[all_variable_df['variable'] == var]['ct_code'])[0]
        test_code = np.array(all_variable_df.loc[all_variable_df['variable'] == var.replace('TESTCD', 'TEST')]['ct_code'])[0]
        code_dict = test_code_mapping(testcd_code, test_code)

        # 2. 根据选项填充data_df
        # 比如--TESTCD有三个选项， data_df原先有10行， 那么填充之后有30行， 相当于一行变成三行
        # 使用字典扩展DataFrame
        column_var_name = var
        column_test_name = var.replace('TESTCD', 'TEST')
        expanded_df = pd.concat([data_df.assign(**{column_var_name: key, column_test_name: value}) for key, value in code_dict.items()], ignore_index=True)

        # data_df = expanded_df这样的赋值操作，即使data_df是可变对象，也会创建一个新的局部变量，这个新的变量只在函数内部有效，不会影响到外部的data_df
        return expanded_df


# 获取--TESTCD和--TEST的选项映射关系（可能是标准术语，也可能是自定义术语）
# 如果是标准术语，则根据术语编码进行映射；如果是自定义术语，则根据键值进行映射
# code-应用名称-数值含义-展示顺序-匹配数据-递交值
# C20197@#$男性@#$2@#$2@#$2@#$M |-| C16576@#$女性@#$1@#$1@#$1@#$F
# 自定义术语的格式： 字典名称-描述-键值-序号-键值
# 例如： 因果关系@#$肯定有关@#$1@#$1@#$1;因果关系@#$可能有关@#$2@#$2@#$2
def test_code_mapping(testcd_code, test_code) -> dict:
    testcd_code_lst = testcd_code.split('|-|')
    test_code_lst = test_code.split('|-|')
    code_dict = {}
    testcd_code_dict = {}
    # 将--TESTCD的编码和递交值映射到字典中
    for i in range(len(testcd_code_lst)):
        testcd_code_split = testcd_code_lst[i].split('@#$')
        if len(testcd_code_split) == 6:  # 标准术语
            testcd_code_dict[testcd_code_split[0]] = testcd_code_split[5]
        else:  # 自定义术语
            testcd_code_dict[testcd_code_split[2]] = testcd_code_split[1]

    # 将--TESTCD的递交值和--TEST的递交值映射到字典中
    for test_code in test_code_lst:
        test_code_split = test_code.split('@#$')
        code = test_code_split[2]  # 键值
        test_value = test_code_split[1]  # 描述

        if len(test_code_split) == 6:  # 标准术语
            code = test_code_split[0]
            test_value = test_code_split[5]
        try:
            testcd_value = testcd_code_dict[code]
            code_dict[testcd_value] = test_value
        except Exception as e:
            logger.error("TESTCD和TEST的选项映射关系获取失败：" + code)
            logger.error(e)
    return code_dict


if __name__ == '__main__':
    test_code = 'RSTEST-症状问题@#$咳嗽程度@#$4@#$4@#$4|-|RSTEST-症状问题@#$是否有呼吸道症状@#$2@#$2@#$2|-|RSTEST-症状问题@#$疾病分型@#$5@#$5@#$5|-|RSTEST-症状问题@#$症状@#$3@#$3@#$3|-|RSTEST-症状问题@#$症状发作日期@#$1@#$1@#$1'
    testcd_code = 'RSTESTCD-症状问题编码@#$CLICLASS@#$5@#$5@#$5|-|RSTESTCD-症状问题编码@#$COUGHDEG@#$4@#$4@#$4|-|RSTESTCD-症状问题编码@#$MORBIDT@#$1@#$1@#$1|-|RSTESTCD-症状问题编码@#$SYMPTOM@#$3@#$3@#$3|-|RSTESTCD-症状问题编码@#$SYMPTOYN@#$2@#$2@#$2'
    dict = test_code_mapping(testcd_code, test_code)
    # 生成随机数据
    data = {
        'Column1': np.random.randint(0, 100, size=10),
        'Column2': np.random.rand(10),
        'Column3': ['String{}'.format(i) for i in range(10)],
        'Column4': pd.date_range(start='2022-01-01', periods=10, freq='D'),
        'Column5': np.random.choice(['A', 'B', 'C'], size=10)
    }
    df = pd.DataFrame(data)
    # 使用字典扩展DataFrame
    expanded_df = pd.concat([df.assign(TESTCD=key, TEST=value) for key, value in dict.items()], ignore_index=True)

    print(expanded_df)
    # exp = 'if(x1 == "0") { return \'未完成\';}'
    # env = {'x1': [1.0] * 5200}  # 示例，x1列表包含5200个1.0
    # print(derived_variable_generate(exp, env))
